package com.es.poem.controller;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.mapper.EntityWrapper;
import com.baomidou.mybatisplus.mapper.Wrapper;
import com.baomidou.mybatisplus.plugins.Page;
import com.es.poem.config.ResultInfo;
import com.es.poem.entry.Poem;
import com.es.poem.es.PoemES;
import com.es.poem.repository.PoemRepository;
import com.es.poem.service.PoemService;
import com.es.poem.util.RedisUtil;
import com.es.poem.util.SnowFlakeUtil;
import com.google.gson.Gson;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.update.UpdateRequest;
import org.elasticsearch.action.update.UpdateResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.query.*;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * @description: 唐诗控制类
 **/
//@CrossOrigin
@RestController
@RequestMapping("/poem")
public class PoemController {

    private static final String INDEX_NAME = "poem";

    @Autowired
    private PoemRepository poemRepository;

    @Autowired
    private RestHighLevelClient restHighLevelClient;

    @Autowired
    private PoemService poemService;

    @Resource
    private RedisUtil redisUtil;


    @PostMapping("add")
    public ResultInfo add(@RequestBody Poem poem) {
        ResultInfo resultInfo = new ResultInfo();
        //雪花算法分布式id
        SnowFlakeUtil sf = new SnowFlakeUtil();
        poem.setId(sf.nextId());
        //保存至db
        poemService.insert(poem);

        //保存至es
        Gson gs = new Gson();
        PoemES es = gs.fromJson(gs.toJson(poem), PoemES.class);
        poemRepository.save(es);

        return resultInfo.success(poem);
    }

    @DeleteMapping("del")
    public ResultInfo del(@RequestParam("id") Long id) {
        ResultInfo resultInfo = new ResultInfo();
        //db
        poemService.deleteById(id);
        //es
        poemRepository.deleteById(id);

        return resultInfo.success();
    }

    @DeleteMapping("delAllToES")
    public ResultInfo delAll() {
        ResultInfo resultInfo = new ResultInfo();
        //es
        poemRepository.deleteAll();

        return resultInfo.success();
    }

    @PostMapping("upd")
    public ResultInfo upd(@RequestBody Poem poem) throws IOException {
        ResultInfo resultInfo = new ResultInfo();
        //db
        poemService.updateById(poem);
        //es
        UpdateRequest updateRequest = new UpdateRequest(INDEX_NAME, poem.getId().toString());
        updateRequest.doc(JSON.toJSONString(poem), XContentType.JSON);
        UpdateResponse response = restHighLevelClient.update(updateRequest, RequestOptions.DEFAULT);

        return resultInfo.success(poem);
    }

    @GetMapping("get")
    public ResultInfo get(@RequestParam(value = "type", required = true) String type,
                          @RequestParam(value = "keyword", required = true) String keyword,
                          @RequestParam(value = "pageNo", defaultValue = "1", required = false) int pageNo,
                          @RequestParam(value = "pageSize", defaultValue = "10", required = false) int pageSize) throws IOException {
        ResultInfo resultInfo = new ResultInfo();
        boolean flag = true;

        SearchRequest searchRequest = new SearchRequest(INDEX_NAME);
        //创建查询条件对象(所有的条件配置都在这个对象内)
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();

        //创建高亮对象
        HighlightBuilder highlightBuilder = new HighlightBuilder()
                .requireFieldMatch(false)
                .field("*")
                .preTags("<span style='color: red'>")
                .postTags("</span>");
        //返回完整数据
        highlightBuilder.numOfFragments(0);
        //unified(默认)，plain和fvh（fast vector highlighter）
        highlightBuilder.highlighterType("plain");

        searchSourceBuilder.highlighter(highlightBuilder);

        // 分页
        searchSourceBuilder.from((pageNo - 1) * pageSize);
        searchSourceBuilder.size(pageSize);

        BoolQueryBuilder boolQuery = new BoolQueryBuilder();

        //过滤
        //RangeQueryBuilder rangeQuery = QueryBuilders.rangeQuery("content");
        //rangeQuery.equals("这是帅小子");
        //boolQuery.filter(rangeQuery);

        if ("title".equals(type) && !"".equals(keyword)) {
            //方式一
//            DisMaxQueryBuilder disMaxQueryBuilder = QueryBuilders.disMaxQuery();
//            QueryBuilder titleQuery = QueryBuilders.matchQuery("title", poem.getTitle()).boost(2f);
//            disMaxQueryBuilder.add(titleQuery);
            //方式二
            MatchQueryBuilder matchQuery = new MatchQueryBuilder("title", keyword);
            boolQuery.must(matchQuery);
            MatchQueryBuilder pinyinmatchQuery = new MatchQueryBuilder("title.my_pinyin", keyword);
            boolQuery.must(pinyinmatchQuery);
            flag = false;
        }
        if ("author".equals(type) && !"".equals(keyword)) {
            MatchQueryBuilder matchQuery = new MatchQueryBuilder("author", keyword);
            boolQuery.must(matchQuery);
            flag = false;
        }
        if ("content".equals(type) && !"".equals(keyword)) {
            MatchQueryBuilder matchQuery = new MatchQueryBuilder("content", keyword);
            boolQuery.must(matchQuery);
            flag = false;
        }

        if (flag) {
            if (Objects.nonNull(keyword) && !"".equals(keyword)) {
                //相当于
                // GET /poem/_search
                //{
                //  "query": {
                //    "multi_match": {
                //      "query": "查询的值",
                //      "fields": [] //指定字段匹配
                //    }
                //  }
                //}
                searchSourceBuilder.query(QueryBuilders.multiMatchQuery(keyword)).sort("_id", SortOrder.DESC);
            } else {
                //查询所有
                searchSourceBuilder.query(QueryBuilders.matchAllQuery()).sort("_id", SortOrder.DESC);
            }
        } else {
            searchSourceBuilder.query(boolQuery).sort("_id", SortOrder.DESC);
        }


        //将SearchSourceBuilder对象添加到搜索请求中:
        searchRequest.source(searchSourceBuilder);


        SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);

        // 初始化
        List<PoemES> list = new ArrayList<>();

        Integer valueNum = Math.toIntExact(searchResponse.getHits().getTotalHits().value);
        System.out.println("符合条件的记录数: " + valueNum);

        for (SearchHit hit : searchResponse.getHits().getHits()) {
            Map<String, Object> sourceAsMap = hit.getSourceAsMap();

            PoemES poemES = new PoemES();
            poemES.setId(Long.parseLong(hit.getId()));
            poemES.setTitle(sourceAsMap.get("title").toString());
            poemES.setAuthor(sourceAsMap.get("author").toString());
            poemES.setContent(sourceAsMap.get("content").toString());

            //高亮
            Map<String, HighlightField> highlightFields = hit.getHighlightFields();
            if (highlightFields.containsKey("title")) {
                poemES.setTitle(highlightFields.get("title").fragments()[0].toString());
            }
            if (highlightFields.containsKey("title.my_pinyin")) {
                poemES.setTitle(highlightFields.get("title.my_pinyin").fragments()[0].toString());
            }
            if (highlightFields.containsKey("content")) {
                poemES.setContent(highlightFields.get("content").fragments()[0].toString());
            }
            if (highlightFields.containsKey("author")) {
                poemES.setAuthor(highlightFields.get("author").fragments()[0].toString());
            }

            list.add(poemES);
        }

        return resultInfo.success(list, valueNum);
    }

    @GetMapping("getDB")
    public ResultInfo getDb(@RequestParam(value = "type", required = true) String type,
                            @RequestParam(value = "keyword", required = true) String keyword,
                            @RequestParam(value = "pageNo", defaultValue = "1", required = false) int pageNo,
                            @RequestParam(value = "pageSize", defaultValue = "10", required = false) int pageSize) {

        Wrapper<Poem> wrapper = new EntityWrapper<Poem>();
        if ("title".equals(type)) {
            wrapper.like("title", keyword);
        }
        if ("author".equals(type)) {
            wrapper.like("author", keyword);
        }
        if ("content".equals(type)) {
            wrapper.like("content", keyword);
        }

        Page<Poem> poemPage = poemService.selectPage(new Page<Poem>(pageNo, pageSize), wrapper);

        Gson gs = new Gson();
        List<PoemES> list = gs.fromJson(gs.toJson(poemPage.getRecords()), List.class);
        int valueNum = Math.toIntExact(poemPage.getTotal());

        return new ResultInfo().success(list, valueNum);
    }



    //爬虫插入数据到数据库
    @PostMapping("addToDB")
    public ResultInfo addToDB() throws Exception {
        ResultInfo resultInfo = new ResultInfo();
        //批量插入
        return resultInfo.success();
    }


    //插入db数据到es
    @PostMapping("addToES")
    public ResultInfo addToES() throws Exception {
        ResultInfo resultInfo = new ResultInfo();
        //查找数据库中的所有数据
        List<Poem> poemList = poemService.findAll();

        Iterable<PoemES> iterable = poemList.stream().map(t -> {
            PoemES p = new PoemES();
            BeanUtils.copyProperties(t, p);
            return p;
        }).collect(Collectors.toList());

        //poemRepository.save(new PoemES(11L,"12","22","33"));
        //批量插入es
        Iterable<PoemES> poemES = poemRepository.saveAll(iterable);

        return resultInfo.success(poemES);
    }


    /**
     * 高亮分页查询
     * <p>
     * keyword：查询key
     * pageNo:  页数
     * pageSize：每页大小
     **/
    @GetMapping("/search")
    public ResultInfo search(@RequestParam("keyword") String keyword,
                             @RequestParam("pageNo") int pageNo,
                             @RequestParam("pageSize") int pageSize) throws Exception {

        //创建搜索体
        SearchRequest searchRequest = new SearchRequest(INDEX_NAME);
        //创建搜索条件对象
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
        //创建高亮对象
        HighlightBuilder highlightBuilder = new HighlightBuilder()
                .requireFieldMatch(false)
                .field("*")
                .preTags("<span style='color: red'>")
                .postTags("</span>");

        //对一个内容长度比较长的字段进行搜索并使用高亮显示插件时，通过获得结果中的高亮字段获取的内容只有一部分，而非全部内容
        //当需要获取全部内容时，只需要设置 number_of_fragments 为0 即可返回完整内容
        highlightBuilder.numOfFragments(0);
        //unified(默认)，plain和fvh（fast vector highlighter）
        highlightBuilder.highlighterType("plain");

        sourceBuilder.highlighter(highlightBuilder);

        // 分页
        sourceBuilder.from((pageNo - 1) * pageSize);
        sourceBuilder.size(pageSize);

        //这个只能拆分单个词匹配
        //TermQueryBuilder termQueryBuilder = new TermQueryBuilder("content", keyword);
        //sourceBuilder.query(termQueryBuilder);

        if (Objects.nonNull(keyword) && !"".equals(keyword)) {
            sourceBuilder.query(structureQuery(keyword)).sort("_id", SortOrder.ASC);

            //redis 搜索记录,每次增长值为0.5
            redisUtil.zSetIncrementScore("es-search", keyword, 0.5);
            //获取该value对应的分值
            Double aDouble = redisUtil.zSetScore("es-search", keyword);

            //达到10分，则添加为热词
            if (aDouble >= 10) {
                List<Object> objectList = redisUtil.lGet("es-hotwords", 0, -1);
                boolean contains = objectList.contains(keyword.trim());
                if (!contains) {
                    //写入
                    DicController dicController = new DicController();
                    boolean b = dicController.writeToResource(keyword.trim());
                    if (b) {
                        objectList.add(keyword.trim());
                        redisUtil.lSet("es-hotwords", keyword.trim());
                    }
                }
            }

        } else {
            sourceBuilder.query(QueryBuilders.matchAllQuery()).sort("_id", SortOrder.ASC);
        }

        //超时
        sourceBuilder.timeout(new TimeValue(60, TimeUnit.SECONDS));

        searchRequest.source(sourceBuilder);
        //返回
        SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
        // 初始化
        List<PoemES> list = new ArrayList<>();

        Integer valueNum = Math.toIntExact(searchResponse.getHits().getTotalHits().value);
        System.out.println("符合条件的记录数: " + valueNum);

        for (SearchHit hit : searchResponse.getHits().getHits()) {
            Map<String, Object> sourceAsMap = hit.getSourceAsMap();

            PoemES poemES = new PoemES();
            poemES.setId(Long.parseLong(hit.getId()));
            poemES.setTitle(sourceAsMap.get("title").toString());
            poemES.setAuthor(sourceAsMap.get("author").toString());
            poemES.setContent(sourceAsMap.get("content").toString());

            //高亮
            Map<String, HighlightField> highlightFields = hit.getHighlightFields();
            if (highlightFields.containsKey("title")) {
                poemES.setTitle(highlightFields.get("title").fragments()[0].toString());
            }
//            if (highlightFields.containsKey("title.my_pinyin")) {
//                poemES.setTitle(highlightFields.get("title.my_pinyin").fragments()[0].toString());
//            }
            if (highlightFields.containsKey("content")) {
                poemES.setContent(highlightFields.get("content").fragments()[0].toString());
            }
            if (highlightFields.containsKey("author")) {
                poemES.setAuthor(highlightFields.get("author").fragments()[0].toString());
            }

            list.add(poemES);
        }

        ResultInfo resultInfo = new ResultInfo();
        return resultInfo.success(list, valueNum);
    }

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    /**
     * 获取每次点击给相关词
     * <p>
     * https://blog.csdn.net/zuihongyan518/article/details/80926501
     */
    @GetMapping(value = "/getementScore")
    public ResultInfo getementScore(@RequestParam("key") String key) {
        ResultInfo resultInfo = new ResultInfo();
        return resultInfo.success(redisUtil.zSetRange(key, 0, -1));
    }

    /**
     * 获取变量中元素的索引,下标开始位置为0。
     * <p>
     * https://blog.csdn.net/zuihongyan518/article/details/80926501
     */
    @GetMapping(value = "/rank")
    public ResultInfo rank(@RequestParam("key") String key, @RequestParam("value") String value) {
        Long rank = redisTemplate.opsForZSet().rank(key, value);

        ResultInfo resultInfo = new ResultInfo();
        return resultInfo.success(rank);
    }

    //获取对应的集合信息（包括分值score）
    @GetMapping(value = "/rangeWithScores")
    public ResultInfo rangeWithScores(@RequestParam("key") String key) {
        ResultInfo resultInfo = new ResultInfo();
        Set<ZSetOperations.TypedTuple<Object>> typedTuples = redisUtil.zSetrangeWithScores(key, 0, -1);

        List<Map<String, Object>> maps = new ArrayList<>();

        typedTuples.stream().forEach(t ->
        {
            Map<String, Object> map = new HashMap<>();
            //下标
            Long rank = redisTemplate.opsForZSet().rank(key, t.getValue());
            map.put("id", rank.intValue() + 1);

            Object o = redisUtil.get(t.getValue().toString());
            map.put("date", o);

            Double score = t.getScore();
            if (score % 0.5 == 0) {
                map.put("number", new Double(score / 0.5).intValue());
            } else {
                map.put("number", new Double((score / 0.5) + 1).intValue());
            }
            map.put("name", t.getValue());
            map.put("score", t.getScore());

            maps.add(map);
        });
        return resultInfo.success(maps);
    }

    /**
     * 每次点击给相关词热度+1
     */
    @GetMapping(value = "/incrementScore")
    public ResultInfo incrementScore(@RequestParam("key") String key, @RequestParam("value") String value, @RequestParam("score") double score) {
        ResultInfo resultInfo = new ResultInfo();
        return resultInfo.success(redisUtil.zSetIncrementScore(key, value, score));
    }

    /**
     * 删除相关词搜索热度
     */
    @GetMapping(value = "/removeementScore")
    public ResultInfo removeementScore(@RequestParam("key") String key, @RequestParam("value") String value) {
        ResultInfo resultInfo = new ResultInfo();
        return resultInfo.success(redisUtil.zSetRemove(key, value));
    }

    /**
     * 删除所有曾搜索过的相关词
     */
    @GetMapping(value = "/removeAll")
    public ResultInfo removeAll(@RequestParam("key") String key) {
        ResultInfo resultInfo = new ResultInfo();
        return resultInfo.success(redisUtil.zSetRemoveAll(key));
    }


    /**
     * 中文、拼音混合搜索
     */
    public DisMaxQueryBuilder structureQuery(String content) {
        //使用dis_max直接取多个query中，分数最高的那一个query的分数即可
        DisMaxQueryBuilder disMaxQueryBuilder = QueryBuilders.disMaxQuery();
        //boost 设置权重,搜索匹配
        QueryBuilder titleQuery = QueryBuilders.matchQuery("title", content).boost(2f);
        QueryBuilder pinyinTitleQuery = QueryBuilders.matchQuery("title.my_pinyin", content);
        QueryBuilder authorQuery = QueryBuilders.matchQuery("author", content).boost(2f);
        QueryBuilder contentQuery = QueryBuilders.matchQuery("content", content).boost(2f);

        disMaxQueryBuilder.add(titleQuery);
        disMaxQueryBuilder.add(pinyinTitleQuery);
        disMaxQueryBuilder.add(authorQuery);
        disMaxQueryBuilder.add(contentQuery);
        return disMaxQueryBuilder;
    }
}
